import 'package:flutter/foundation.dart';
import 'package:gobelieve/common/utils/global.dart';
import 'package:gobelieve/model/conversation.dart';
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:uuid/uuid.dart';
import 'package:buffer/buffer.dart';
import 'dart:typed_data';

Map<int, String> cmdMap = {};

class ImService with ChangeNotifier {
  static const HEADER_SIZE = 12;
  static const VERSION = 1;
  static const STATE_UNCONNECTED = 0;
  static const STATE_CONNECTING = 1;
  static const STATE_CONNECTED = 2;
  static const STATE_CONNECTFAIL = 3;
  static const STATE_AUTHENTICATION_FAIL = 4;
  static const ACK_TIMEOUT = 5; //ack 超时5s,主动断开socket
  static const MSG_AUTH_STATUS = 3;
  static const MSG_IM = 4;
  static const MSG_ACK = 5;
  static const MSG_RST = 6;
  static const MSG_GROUP_NOTIFICATION = 7;
  static const MSG_GROUP_IM = 8;
  static const MSG_PEER_ACK = 9;
  static const MSG_PING = 13;
  static const MSG_PONG = 14;
  static const MSG_AUTH_TOKEN = 15;
  static const MSG_RT = 17;
  static const MSG_ENTER_ROOM = 18;
  static const MSG_LEAVE_ROOM = 19;
  static const MSG_ROOM_IM = 20;
  static const MSG_SYSTEM = 21;
  static const MSG_CUSTOMER = 24;
  static const MSG_CUSTOMER_SUPPORT = 25;
  static const MSG_SYNC = 26;
  static const MSG_SYNC_BEGIN = 27;
  static const MSG_SYNC_END = 28;
  static const MSG_SYNC_NOTIFY = 29;
  static const MSG_SYNC_GROUP = 30;
  static const MSG_SYNC_GROUP_BEGIN = 31;
  static const MSG_SYNC_GROUP_END = 32;
  static const MSG_SYNC_GROUP_NOTIFY = 33;
  static const MSG_SYNC_KEY = 34;
  static const MSG_GROUP_SYNC_KEY = 35;
  static const MSG_NOTIFICATION = 36;
  static const MSG_METADATA = 37;
  //消息标志
  static const MESSAGE_FLAG_TEXT = 1;
  static const MESSAGE_FLAG_UNPERSISTENT = 2;
  static const MESSAGE_FLAG_SELF = 8;
  static const MESSAGE_FLAG_PUSH = 16;
  static const MESSAGE_FLAG_SUPER_GROUP = 32;
  static const PLATFORM_ID = 3;
  static const HEARTBEAT = 60 * 3; // 60 * 3;

  String imHost; // 服务器
  int imPort; // 端口号
  String accessToken; // token
  String deviceId; // 设备ID
  int seq = 0; // 本地序列
  int connectState = 0; // 连接状态
  int connectFailCount = 0; // 失败次数
  int syncKey = 0; // 消息同步key
  Map groupSyncKeys; // 群组消息同步key
  int pingTimestamp; // ping时间戳
  bool stopped = true; // 本地处理器是否停止运行
  Socket socket;
  Map observer = {};
  Timer pingTimer; // ping逻辑
  Map metaMessage;

  ///sending message
  Map messages = {};
  Map groupMessages = {};
  // this.customerMessages = {};
  bool isSyncing = false;
  int syncTimestamp = 0;
  int pendingSyncKey = 0;
  // 运行时消息列表 String：CMD-id  消息类型-所属ID：4-11111表示和11111用户的单聊消息，8-1表示群组1的消息列表
  Map<String, Set<dynamic>> messageHisMap = {};

  // app运行时动态渲染数据
  List<Conversation> conversationList = Conversation.mockConversations;

  ImService(); // this.imHost, this.imPort
  Future connect() async {
    print("connect···");
    if (this.stopped) {
      print("im service stopped");
      return;
    }
    if (this.socket != null) {
      print("socket is't null");
      return;
    }
    this.connectState = ImService.STATE_CONNECTING;
    this.callStateObserver();
    try {
      socket = await Socket.connect(this.imHost, this.imPort,
          timeout: Duration(seconds: 2));
      this.socket = socket;
      this.connectState = ImService.STATE_CONNECTED;
      this.callStateObserver();

      socket.listen(onMessage,
          onError: onError, onDone: doneHandle, cancelOnError: false);
    } catch (e) {
      print("连接socket出现异常，e=${e.toString()}");
    }
  }

  void callStateObserver() {
    switch (this.connectState) {
      case ImService.STATE_UNCONNECTED:
        print("socket unconnected");
        break;
      case ImService.STATE_CONNECTING:
        print("socket connecting");
        break;
      case ImService.STATE_CONNECTED:
        print("socket connected");
        break;
      case ImService.STATE_CONNECTFAIL:
        print("socket connect fail");
        break;
      case ImService.STATE_AUTHENTICATION_FAIL:
        print("socket authentication fail");
        break;
      default:
    }
    // TODO 暂时先不管状态变更
  }

  /// 解码处理方法
  void onMessage(newData) {
    // print("onmessage···");
    //拼凑当前最新未处理的网络数据
    var cacheData = Uint8List.fromList(newData);
    ByteDataReader reader = ByteDataReader();
    //读入数据
    reader.add(cacheData);
    //缓存数据长度符合最小包长度才尝试解码
    while (reader.remainingLength >= 12) {
      // print("reader.remainingLength:${reader.remainingLength}");
      var msgLen = reader.readUint32();
      //数据长度小于消息长度，说明不是完整的数据，暂不处理
      if (cacheData.length < msgLen) {
        G.toast("收到的消息不完整！");
        return;
      }
      int msgSeq = reader.readUint32();
      //读取消息号
      int msgCmd = reader.readUint8();
      int msgVersion = reader.readUint8();
      int msgFlag = reader.readUint8();
      var isSelf = (msgFlag & ImService.MESSAGE_FLAG_SELF) == 1;
      print(
          "收到消息 长度: $msgLen,序列: $msgSeq,类型: ${cmdMap[msgCmd]},版本: $msgVersion, 标识:$msgFlag");
      reader.readUint8(); // read header末尾的0
      var msg = {};
      msg["seq"] = msgSeq;
      msg["cmd"] = msgCmd;
      msg["flag"] = msgFlag;
      // 解析消息
      switch (msgCmd) {
        case ImService.MSG_IM:
          Uint8List content;
          msg["isSelf"] = isSelf;
          msg["sender"] = reader.readInt64();
          msg["receiver"] = reader.readInt64();
          if (msgVersion == 0) {
            reader.readUint32(); // msgId
            content = reader.read(msgLen - 20);
          } else {
            msg["timestamp"] = reader.readUint32();
            reader.readUint32(); // msgId
            content = reader.read(msgLen - 24);
          }
          try {
            msg["content"] = utf8.decode(content);
          } catch (e) {
            msg["content"] = {"text": "解析错误"}.toString();
            print("解析content报错");
          }
          break;
        case ImService.MSG_GROUP_IM:
          msg["isSelf"] = isSelf;
          msg["sender"] = reader.readUint64();
          msg["receiver"] = reader.readUint64();
          if (msgVersion == 0) {
            print(reader.readUint32()); // msgId
            msg["content"] = utf8.decode(reader.read(msgLen - 20));
          } else {
            msg["timestamp"] = reader.readUint32();
            print(reader.readUint32()); // msgId
            msg["content"] = utf8.decode(reader.read(msgLen - 24));
          }
          break;
        case ImService.MSG_AUTH_STATUS:
          msg["authStatus"] = reader.readUint32();
          break;
        case ImService.MSG_GROUP_NOTIFICATION:
          msg["content"] = utf8.decode(reader.read(msgLen));
          break;
        case ImService.MSG_ACK:
          msg["ack"] = reader.readUint32();
          break;
        case ImService.MSG_SYNC_NOTIFY:
          msg["syncKey"] = reader.readUint64();
          break;
        case ImService.MSG_SYNC_BEGIN:
          msg["syncKey"] = reader.readUint64();
          break;
        case ImService.MSG_SYNC_END:
          msg["syncKey"] = reader.readUint64();
          break;
        case ImService.MSG_SYNC_GROUP_NOTIFY:
          msg["groupID"] = reader.readUint64();
          msg["syncKey"] = reader.readUint64();
          break;
        case ImService.MSG_SYNC_GROUP_BEGIN:
          msg["groupID"] = reader.readUint64();
          msg["syncKey"] = reader.readUint64();
          break;
        case ImService.MSG_SYNC_GROUP_END:
          msg["groupID"] = reader.readUint64();
          msg["syncKey"] = reader.readUint64();
          break;
        case ImService.MSG_SYSTEM:
          msg["content"] = utf8.decode(reader.read(msgLen));
          break;
        case ImService.MSG_NOTIFICATION:
          msg["content"] = utf8.decode(reader.read(msgLen));
          break;
        case ImService.MSG_METADATA:
          msg["syncKey"] = reader.readUint64();
          msg["prevSyncKey"] = reader.readUint64();
          reader.read(16); // padding=16
          break;
        case ImService.MSG_PONG:
          break;
        default:
          print("CMD 未处理:$msgCmd");
      }
      this.handleMessage(msg);
      // cacheData = cacheData.sublist(msgLen + 12);
    }
  }

  bool send(int cmd, Uint8List body, [nonpersistent = false]) {
    if (this.socket == null) {
      G.toast("socket链接失败！");
      return false;
    }
    if (this.connectState != ImService.STATE_CONNECTED) {
      return false;
    }
    this.seq++;
    ByteDataWriter contentWrite = ByteDataWriter();
    contentWrite.writeUint32(body.length); // len(4)
    contentWrite.writeUint32(this.seq); // seq(4)
    contentWrite.writeUint8(cmd); // cmd(1)
    contentWrite.writeUint8(ImService.VERSION); // version(1)
    if (nonpersistent) {
      contentWrite.writeUint8(ImService.MESSAGE_FLAG_UNPERSISTENT); // flag(1)
    } else {
      contentWrite.writeUint8(0);
    }
    contentWrite.writeUint8(0); // 0(1)
    contentWrite.write(body); // body
    this.socket.add(contentWrite.toBytes());
    return true;
  }

  void sendSync(syncKey) {
    ByteDataWriter writer = ByteDataWriter();
    writer.writeUint64(syncKey);
    this.send(ImService.MSG_SYNC, writer.toBytes());
  }

  void sendPing() {
    var body = new Uint8List(0);
    this.send(ImService.MSG_PING, body);
    this.pingTimestamp = new DateTime.now().millisecondsSinceEpoch;
  }

  void sendAuth() {
    ByteDataWriter writer = ByteDataWriter();
    writer.writeUint8(ImService.PLATFORM_ID);
    var accessToken = new Uint8List.fromList(this.accessToken.codeUnits);
    var len = accessToken.length;
    writer.writeUint8(len);
    writer.write(accessToken);
    var deviceId = new Uint8List.fromList(this.deviceId.codeUnits);
    len = deviceId.length;
    writer.writeUint8(len);
    writer.write(deviceId);
    var body = writer.toBytes();
    this.send(ImService.MSG_AUTH_TOKEN, body);
  }

  Future start() async {
    // 消息处理器添加
    this.observer[3] = handleAuthStatus; // 鉴权结果处理 cmd=3
    this.observer[14] = handlePong;
    // 👆上面是用reader的，下面是用decode好的msg的
    this.observer[4] = handlePeerMessage;

    if (!this.stopped) {
      // print("im service already be started");
      return;
    }
    print("start im service");
    this.stopped = false;
    await this.connect();
    this.sendAuth();
    this.pendingSyncKey = 0;
    // DONE 周期性ping添加 3分钟一个ping
    this.pingTimer =
        Timer.periodic(Duration(seconds: ImService.HEARTBEAT), (t) {
      this.ping();
    });
  }

  String guid() {
    return Uuid().v4();
  }

  void onError(data) {
    G.toast("socket err!");
  }

  void handleMessage(Map msg) {
    var seq = msg["seq"]; // sendAck 用的
    var cmd = msg["cmd"];
    // print("handle message : $msg");
    if ((msg["flag"] & ImService.MESSAGE_FLAG_PUSH) == 1) {
      var metaMessage = this.metaMessage;
      this.metaMessage = {};
      var metadata = {};
      if (metaMessage == null) {
        return;
      }
      if (metaMessage["seq"] + 1 == msg["seq"]) {
        metadata = metaMessage;
      } else {
        return;
      }
      if (metadata["prevSyncKey"] == 0 || metadata["syncKey"] == 0) {
        return;
      }
      //校验metadata中的synckey是否连续
      if (msg["flag"] & ImService.MESSAGE_FLAG_SUPER_GROUP) {
        var groupID;
        if (cmd == ImService.MSG_GROUP_IM) {
          groupID = msg["receiver"];
        } else {
          return;
        }
        var groupSyncKey = 0;
        if (this.groupSyncKeys.containsKey(groupID)) {
          groupSyncKey = this.groupSyncKeys[groupID];
        }
        if (metadata["prevSyncKey"] != groupSyncKey) {
          print(
              "sync key is not sequence: ${metadata["prevSyncKey"]}----$groupSyncKey");
          return;
        }
      } else {
        if (metadata["prevSyncKey"] != this.syncKey) {
          print(
              "sync key is not sequence:${metadata["prevSyncKey"]}----$this.syncKey");
          return;
        }
      }
    }
    switch (cmd) {
      case ImService.MSG_IM:
        //处理消息 直接传输reader，方便处理。
        if (this.observer.containsKey(cmd)) {
          observer[cmd](msg);
        }
        this.sendAck(seq);
        break;
      case ImService.MSG_METADATA:
        this.metaMessage = msg;
        break;
      case ImService.MSG_SYNC_BEGIN:
        var newSyncKey = msg["syncKey"];
        // print("sync begin: $newSyncKey");
        break;
      case ImService.MSG_SYNC_END:
        var newSyncKey = msg["syncKey"];
        // print("sync begin: $newSyncKey");
        if (newSyncKey != this.syncKey) {
          this.syncKey = newSyncKey;
          this.sendSyncKey(this.syncKey);
          //TODO saveSyncKey  handleSyncEnd
        }
        this.isSyncing = false;
        if (this.pendingSyncKey > this.syncKey) {
          this.sendSync(this.syncKey);
          this.isSyncing = true;
          this.syncTimestamp = new DateTime.now().millisecondsSinceEpoch;
          this.pendingSyncKey = 0;
        }
        break;
      case ImService.MSG_SYNC_NOTIFY:
        var newSyncKey = msg["syncKey"];
        print("sync notify:$newSyncKey");

        var isSyncing = this.isSyncing &&
            (new DateTime.now().millisecondsSinceEpoch - this.syncTimestamp <
                4000);
        if (!isSyncing && this.syncKey < newSyncKey) {
          this.sendSync(this.syncKey);
          this.isSyncing = true;
          this.syncTimestamp = new DateTime.now().millisecondsSinceEpoch;
          this.pendingSyncKey = 0;
        } else if (newSyncKey > this.pendingSyncKey) {
          this.pendingSyncKey = newSyncKey;
        }
        break;
      case ImService.MSG_PONG:
        // print("pong···");
        this.pingTimestamp = 0;
        break;
      case ImService.MSG_AUTH_STATUS:
        if (this.observer.containsKey(cmd)) {
          observer[cmd](msg);
        }
        break;
      default:
        print("没有CMD：$cmd 对应的消息处理器！");
    }
  }

  void sendSyncKey(syncKey) {
    ByteDataWriter writer = ByteDataWriter();
    writer.writeUint64(syncKey);
    this.send(ImService.MSG_SYNC_KEY, writer.toBytes());
  }

  void sendAck(int seq) {
    ByteDataWriter writer = ByteDataWriter();
    writer.writeUint32(seq);
    this.send(ImService.MSG_ACK, writer.toBytes());
  }

  void doneHandle() {}
  void handleAuthStatus(Map msg) {
    // print("authStatus: ${msg["authStatus"]}");
    if (msg["authStatus"] != 0) {
      this.connectState = ImService.STATE_AUTHENTICATION_FAIL;
      this.connectFailCount++; // 鉴权失败，错误次数加一
      this.callStateObserver();
    } else {
      this.connectFailCount = 0; // 鉴权成功，重置连接失败次数
      this.sendSync(this.syncKey);
      this.isSyncing = true;
      var now = new DateTime.now().millisecondsSinceEpoch;
      this.syncTimestamp = now;
    }
  }

  void handlePong() {
    // print("pong");
    this.pingTimestamp = 0;
  }

  void ping() {
    if (this.connectState != ImService.STATE_CONNECTED) {
      return;
    }
    // print("ping...");
    this.sendPing();
    if (this.pingTimestamp == 0) {
      this.pingTimestamp = new DateTime.now().millisecondsSinceEpoch.floor();
    }

    var self = this;
    new Timer(new Duration(milliseconds: 3100), () {
      var now = new DateTime.now().millisecondsSinceEpoch;
      if (self.pingTimestamp > 0 && now - self.pingTimestamp >= 3) {
        print("ping timeout");
        if (self.connectState == ImService.STATE_CONNECTED) {
          //trigger close event
          self.socket.close();
          self.socket = null;
        }
      }
    });
  }

  bool sendPeerMessage(Map msg) {
    if (this.connectState != ImService.STATE_CONNECTED) {
      return false;
    }
    ByteDataWriter writer = ByteDataWriter();
    var content = utf8.encode(msg["content"]);
    var ts = msg["timestamp"];
    var msgId = msg["msgLocalID"] ?? 0;
    writer.writeUint64(msg["sender"]);
    writer.writeUint64(msg["receiver"]);
    writer.writeUint32(ts);
    writer.writeUint32(msgId);
    writer.write(content);
    var r = this.send(ImService.MSG_IM, writer.toBytes());
    if (!r) {
      return false;
    }
    msg["__sendTimestamp__"] = new DateTime.now().millisecondsSinceEpoch / 1000;
    this.messages[this.seq] = msg;
    var self = this;
    Future.delayed(Duration(milliseconds: ImService.ACK_TIMEOUT * 1000 + 100),
        () async {
      await self.checkAckTimeout();
    });
    // TODO 持久化到本地数据库
    // print("已发送");
    return true;
  }

  void handlePeerMessage(Map msg) {
    // print("收到${msg['sender']} 发来的消息，内容：${msg['content']}");
    var content = jsonDecode(msg['content']);
    print("content; $content");
    content['type'] = 0;
    var uid = G.user.data.uid;
    var fromUid = msg['sender'];
    if (uid == msg['sender']) {
      content['type'] = 1;
      fromUid = msg['receiver'];
    }
    var hisMapKey = '4-' + fromUid.toString();
    var notHisMsg = false;
    if (this.isSyncing) {
      // 暂时使用该逻辑消息去重
      if (this.messageHisMap.containsKey(hisMapKey)) {
        notHisMsg = this.messageHisMap[hisMapKey].add(content);
      } else {
        var hisSet = new Set();
        notHisMsg = hisSet.add(content);
        this.messageHisMap[hisMapKey] = hisSet;
      }
      // 更新会话列表和历史消息Set
      Conversation valTmp;
      this.conversationList.forEach((Conversation val) {
        if ((val.userId == msg['sender'] || val.userId == msg['receiver']) &&
            notHisMsg) {
          valTmp = val;
          // this.conversationList.remove(val);
          valTmp.des = jsonDecode(msg['content'])['text'] ?? '非文本消息';
          valTmp.unreadMsgCount += 1;
        }
      });
      if (valTmp == null) {
        valTmp = Conversation.fromImMsg(msg);
      }
      this.conversationList.removeWhere((value) =>
          value.userId == msg['sender'] || value.userId == msg['receiver']);
      this.conversationList.insert(0, valTmp);
      this.notifyListeners();
      if (notHisMsg) {
        // TODO 消息存储到sqlite

      }
    }
  }

  /// 初始化
  init(String token) async {
    cmdMap[MSG_AUTH_STATUS] = "MSG_AUTH_STATUS";
    cmdMap[MSG_IM] = "MSG_IM";
    cmdMap[MSG_ACK] = "MSG_ACK";
    cmdMap[MSG_GROUP_NOTIFICATION] = "MSG_GROUP_NOTIFICATION";
    cmdMap[MSG_GROUP_IM] = "MSG_GROUP_IM";
    cmdMap[MSG_PING] = "MSG_PING";
    cmdMap[MSG_PONG] = "MSG_PONG";
    cmdMap[MSG_AUTH_TOKEN] = "MSG_AUTH_TOKEN";
    cmdMap[MSG_RT] = "MSG_RT";
    cmdMap[MSG_ENTER_ROOM] = "MSG_ENTER_ROOM";
    cmdMap[MSG_LEAVE_ROOM] = "MSG_LEAVE_ROOM";
    cmdMap[MSG_ROOM_IM] = "MSG_ROOM_IM";
    cmdMap[MSG_SYSTEM] = "MSG_SYSTEM";
    cmdMap[MSG_CUSTOMER] = "MSG_CUSTOMER";
    cmdMap[MSG_CUSTOMER_SUPPORT] = "MSG_CUSTOMER_SUPPORT";

    cmdMap[MSG_SYNC] = "MSG_SYNC";
    cmdMap[MSG_SYNC_BEGIN] = "MSG_SYNC_BEGIN";
    cmdMap[MSG_SYNC_END] = "MSG_SYNC_END";
    cmdMap[MSG_SYNC_NOTIFY] = "MSG_SYNC_NOTIFY";

    cmdMap[MSG_SYNC_GROUP] = "MSG_SYNC_GROUP";
    cmdMap[MSG_SYNC_GROUP_BEGIN] = "MSG_SYNC_GROUP_BEGIN";
    cmdMap[MSG_SYNC_GROUP_END] = "MSG_SYNC_GROUP_END";
    cmdMap[MSG_SYNC_GROUP_NOTIFY] = "MSG_SYNC_GROUP_NOTIFY";

    cmdMap[MSG_NOTIFICATION] = "MSG_NOTIFICATION";
    cmdMap[MSG_METADATA] = "MSG_METADATA";
    this.imHost = "47.94.143.28";
    // this.imHost = "192.168.0.242";
    this.imPort = 23000;
    this.accessToken = token; // 弄了个不过期的token
    // "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdGFuZGFyZENsYWltcyI6eyJleHAiOjE2MDkzMTg4NzIsImp0aSI6Ijc6MjIyMjIiLCJpYXQiOjE2MDkzMTUyNzJ9LCJSZWZyZXNoVGltZSI6MTYwOTMxNzA3Mn0.6TSaRUypevQr_lS2B3OdDOxJEGm3JOWjJUv5CcXLoE4";
    this.deviceId = this.guid();
    await this.start();
  }

  Future checkAckTimeout() async {
    var now = new DateTime.now().millisecondsSinceEpoch / 1000;
    var isTimeout = false;
    this.messages.forEach((ack, v) {
      var msg = this.messages[ack];
      if (now - msg["__sendTimestamp__"] >= ImService.ACK_TIMEOUT) {
        isTimeout = true;
      }
    });
    this.groupMessages.forEach((ack, v) {
      var msg = this.groupMessages[ack];
      if (now - msg["__sendTimestamp__"] >= ImService.ACK_TIMEOUT) {
        isTimeout = true;
      }
    });
    if (isTimeout) {
      print("ack timeout, close socket");
      this.doneHandle();
    }
  }

  List hisMsg(int uid, int type) {
    String msgHisKey;
    List res;
    // get history messages
    if (type == 1) {
      // 1 单聊 2 群聊
      msgHisKey = "4-" + uid.toString();
    } else {
      msgHisKey = "8-" + uid.toString();
    }
    if (this.messageHisMap.containsKey(msgHisKey)) {
      res = this.messageHisMap[msgHisKey].toList();
    } else {
      //sqlite里面分页取20条，并赋值给messageHisMap

    }
    // print("res: $res");
    // res?.add({"text": "自己", "type": 1});
    return res;
  }

  void addLocalMessage(String key, Map<String, dynamic> content) {
    this.messageHisMap[key].add(content);
    this.notifyListeners();
  }
}
